#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <pty.h>

#include <sys/select.h>
#include <ncurses.h>
#include <fcntl.h>

#include <sys/epoll.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

#define node_init(root) do { root->prev = root; root->next = root; } while (0)
#define node_append(root, n) do { n->prev = root->prev; n->next = root; n->prev->next = n; n->next->prev = n; } while (0);

typedef struct desk desk_t;
typedef struct win win_t;

struct win {
    win_t *prev;
    win_t *next;
    int pt; // pty fd
    int x, y, w, h;
    struct epoll_event ev;
};

struct desk {
    desk_t *prev;
    desk_t *next;
    win_t *root;
    win_t *focus;
};

int uconf;

desk_t *ROOT = NULL;
desk_t *FOCUS = NULL;

int epfd = -1;
struct epoll_event epevent[128];

//

static FILE *dbg = NULL;

void debug_init(const char *f)
{
    dbg = fopen(f, "w");
}

void debug(const char *str, ...)
{
    if (dbg) {
        va_list ap;
        va_start(ap, str);
        vfprintf(dbg, str, ap);
        va_end(ap);
    }
}

void fatal(const char *errstr, ...) 
{
    va_list ap;
    va_start(ap, errstr);
    vfprintf(stderr, errstr, ap);
    va_end(ap);
    exit(1);
}

void warn(void)
{
}

static void safewrite(int fd, const char *b, size_t n) /* Write, checking for errors. */
{
    size_t w = 0;
    while (w < n){
        ssize_t s = write(fd, b + w, n - w);
        if (s < 0 && errno != EINTR)
            return;
        else if (s < 0)
            s = 0;
        w += (size_t)s;
    }
}

static const char *getshell(void)
{
    char *sh = getenv("SHELL");
    return (sh) ? sh : "/bin/sh";
}

static const char *getterm(void)
{
    char *tm = getenv("TERM");
    return (tm) ? tm : "xterm";
}


void ctrlcode(uint8_t ascii)
{
}

int winnew(desk_t *d)
{
    struct winsize ws = {.ws_row = 30, .ws_col = 80};

    win_t *w = calloc(1, sizeof(win_t));
    if (d->root) {
        node_append(d->root, w);
    } else {
        node_init(w);
        d->root = w;
    }
    d->focus = w;

    pid_t pid = forkpty(&w->pt, NULL, NULL, &ws);
    if (pid < 0){
        perror("forkpty");
        return -1;
    } else if (pid == 0){
        char buf[100] = {0};
        snprintf(buf, sizeof(buf) - 1, "%d %d", getppid(), getpid());
        setsid();
        setenv("MTM", buf, 1);
        setenv("TERM", getterm(), 1);
        signal(SIGCHLD, SIG_DFL);
        execl(getshell(), getshell(), NULL);
        return -7;
    }

    fcntl(w->pt, F_SETFL, O_NONBLOCK);

    w->ev.events = EPOLLIN;
    w->ev.data.ptr = w;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, w->pt, &w->ev) < 0) {
        return -1;
    }

    return 0;
}

desk_t *desknew()
{
    desk_t *d = calloc(1, sizeof(desk_t));
    if (ROOT) {
        node_append(ROOT, d);
    } else {
        node_init(d);
        ROOT = d;
    }
    FOCUS = d;

    winnew(d);
    return d;
}

void deskdel()
{
}

void handlechar()
{

}

static int run(void)
{
    static char iobuf[1024];

    while (ROOT->root) {
        /* epoll wait */
        int n = epoll_wait(epfd, epevent, ARRAY_SIZE(epevent), -1);
        if (n < 0) {
            printf("epoll wait error!\n");
            return -1;
        }

        for (int i = 0; i < n; i++) {
            void *ptr = epevent[i].data.ptr;
            if (ptr == &epfd) { // console
                int r = read(STDIN_FILENO, iobuf, sizeof(iobuf));
                write(FOCUS->focus->pt, iobuf, r);
            } else { // pty
                win_t *win = (win_t *)ptr;
                int r = read(win->pt, iobuf, sizeof(iobuf));
                if (r > 0) {
                    write(STDOUT_FILENO, iobuf, r);
                } else {
                    debug("[pt %d] read failed: %d\n", win->pt, r);
                    return -1;
                }
            }
        }
    }

    return 0;
}

int main()
{
    epfd = epoll_create(1);

    struct epoll_event ev = {
        .events   = EPOLLIN,
        .data.ptr = &epfd, // just recognize console
    };

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) < 0) {
        return -1;
    }

    debug_init("/tmp/log");

    debug("console pid: %d\n", getpid());

    //FD_SET(STDIN_FILENO, &fds);
    signal(SIGCHLD, SIG_IGN);

    initscr();
    printf("shell: %s\n", getshell());
    printf("term : %s\n", getterm());
    raw();
    noecho();
    nonl();

    desknew();
    run();

    //ttynew(NULL, 0, 0, 100, 32);
    endwin();

    fclose(dbg);

    printf("[GT] Bye-Bye\n");
    return 0;
}
